knitr::opts_chunk$set(echo = TRUE)
library(config) # Utilisation d'un fichier de configuration, cf  https://db.rstudio.com/best-practices/managing-credentials/#stored-in-a-file-with-config
library(tidyverse)
library(DBI) # pour les connexions aux BDD
library(RPostgreSQL) # driver postgres
library(RMariaDB) # driver MariaDB
library(RMySQL) # driver MySQL
library(lubridate) # calcul sur les dates

library(httr) #
library(jsonlite) # Gestion format json

library("readxl") # Lecture de fichiers xlsx

library(sf)
library(mapview)
library(plotly) # Graphiques interactifs
library(leaflet) # Cartes interactives

# Fonction pour traduire la réponse de l'API
api_reponse <- function(request) {
  
case_when(request$status_code==200 ~ "OK, tous les résultats sont présents dans la réponse",
          request$status_code==206 ~ "OK, il reste des résultats",
          request$status_code==400 ~ "Requête incorrecte",
            TRUE ~ "Autre réponse")
}

# Fonction pour requêter l'API Hub'Eau

get_hubeau <- function(path, query) {
  
  response <- GET(url = path, query = query) %>% 
  content(as = "text", encoding = "UTF-8") %>%
  fromJSON(flatten = TRUE)
  
data = response$data # Le jeu de données est dans l'objet "data"

if(response$count > query$size) { # Si la taille de page est plus petite que le nb de résultats, faire une boucle pour les pages suivantes
  
pages <- ceiling(response$count/query$size)

# Affichage d'une barre de progression
pb <- winProgressBar(title = "Récupération des chroniques", min = 0,
                     max = pages, width = 300)

for(i in 2:pages){
  
  query$page <- i
  
  response_i <- GET(url = path, query = query) %>% 
  content(as = "text", encoding = "UTF-8") %>%
  fromJSON(flatten = TRUE)

data <- rbind(data, response_i$data) # Concaténation des lignes récupérées

setWinProgressBar(pb, i, title=paste( round(i/pages*100, 0), "% chargé")) # Avancement de la barre de progression

} # Fin boucle for

close(pb)

} # Fin if

return(data)
  } # Fin get_hubeau

# Insertion de données

db.insertion <- function(con,table,data) {
  
  # Date de la dernière donnée en base
  Date_max <- tbl(con, table) %>% summarise(Date_max = max(Date_de_la_mesure, na.rm = TRUE)) %>% pull(Date_max)
  if(is.na(Date_max)) Date_max <- as.Date(params$date_debut)-1
  
  # Données plus récentes à insérer
  data <- data %>% filter(Date_de_la_mesure > Date_max)
  
  # Existe-t-il des données à insérer ?
if (isTRUE(data %>% tally() > 0)) {
  
  # Insertion des nouvelles lignes
  dbWriteTable(con, table, data, overwrite=FALSE, append=TRUE,
             fileEncoding="latin1")
  
  # Horodate en commentaire
mise_a_jour <- paste0(format.Date(Sys.Date(),"%d/%m/%Y"), " : Actualisation ",params$actualisation)

dbGetQuery(con, paste0("ALTER TABLE ",table," COMMENT = '",mise_a_jour,"';"))

paste0("Données insérées : ",data%>%tally)

}
  else {
    
    paste0("Aucune donnée à insérer depuis le ", format.Date(Date_max,"%d/%m/%Y"))
  
    }
  
} # fin db.insertion

# Base Mariadb OEB
con_eau_tbi <- dbConnect(RMariaDB::MariaDB(), default.file = '../../.my.cnf', groups="mysql_oeb",
dbname = "eau_tbi")

# Version localhost
con_oeb_tbi <- dbConnect(RMariaDB::MariaDB(), default.file = '../../.my.cnf', groups="mysql_local",
dbname = "oeb_tbi")


con_referentiels <- dbConnect(RMariaDB::MariaDB(), default.file = '../../.my.cnf', groups="mysql_oeb",
dbname = "eau_referentiels")


dbListTables(con_eau_tbi) # Lister les tables de la base
 [1] "aelb_eeme_ce_import"                    "aelb_eeme_es_import"                    "aelb_eeme_mece_description"            
 [4] "aelb_eeme_mees_description"             "oeb_eau_alteration_hydromorphologique"  "oeb_eau_prelevement_eau_brute"         
 [7] "oeb_eau_prelevement_eau_brute_commune"  "oeb_eau_prelevement_eau_brute_ouvrage"  "oeb_eau_qualite_biologique_ce"         
[10] "oeb_eau_qualite_biologique_ce_new"      "oeb_eru"                                "oeb_eru_aglo"                          
[13] "oeb_eru_boue"                           "oeb_eru_steu"                           "oeb_import_donneepe3b1"                
[16] "oeb_surface_culture_rpg2016"            "oeb_tbi_irep_consoind"                  "oeb_tbi_nappe_moy"                     
[19] "oeb_tbi_nitrate_eau_souterraine"        "oeb_tbi_nitrate_eau_souterraine_site"   "oeb_tbi_nitrate_nappe"                 
[22] "oeb_tbi_niveaunappean"                  "oeb_tbi_onde"                           "oeb_tbi_peb"                           
[25] "oeb_tbi_prelevement_tmp"                "oeb_tbi_qualitebio"                     "oeb_tbi_qualitebio_tmp"                
[28] "oeb_tbi_qualitebio_tmp2"                "oeb_tbi_sispea"                         "oeb_tbi_variationnappe_interannuelle"  
[31] "oeb_tbi_variationnappe_intermensuelles" "oeb_tbi_variationnappe_mois"            "oeb_tbi_variationnappe_pz"             
[34] "oeb_tbi_variationnappe_tmp"             "tbi_image_effectifcomplet"              "tbi_image_exportcomplet"               
conf <- config::get("postgres_dev")

con_postgresql_dev <- DBI::dbConnect(odbc::odbc(),
                          Driver       = conf$driver,
                          servername   = conf$server,
                          UID = conf$uid,
                          PWD = conf$pwd,
                          Port = conf$port,
                          database = 'eau',
                             encoding = "latin1")

# Récupération des infos sur les stations de mesure

# Liste des paramètres de la requête
query_sites = list(
  code_departement= params$num_departement,
  format='json',
  size=params$pagination
  )

# Requête des stations
hubeau_sites <- get_hubeau(path = params$api_sites, query = query_sites)

# Conversion au format géographique
sites <- st_as_sf(hubeau_sites, coords = c("coordonnee_x", "coordonnee_y"), 
    crs = 2154, agr = "constant")

# Récupération des résultats d'indices biologiques

# Liste des paramètres de la requête (lot 1 de départements)
query_resultats = list(
                 #code_departement = params$num_departement,
                 code_departement = "29,22,35",
                 code_indice = params$codes_parametres,
                 #les paramètres date_debut et date_fin créent un bug dans l'API https://github.com/BRGM/hubeau/issues/81
                 #date_debut_prelevement = params$date_debut,
                 #date_fin_prelevement = params$date_fin,
                 size = params$pagination,
                 format = 'json')

# Requête des chroniques
hubeau_resultats <- get_hubeau(path = params$api_resultats, query=query_resultats)

# Liste des paramètres de la requête (lot 2 de départements)
query_resultats = list(
                 #code_departement = params$num_departement,
                 code_departement = "56,50,44,49,53",
                 code_indice = params$codes_parametres,
                 #les paramètres date_debut et date_fin créent un bug dans l'API https://github.com/BRGM/hubeau/issues/81
                 #date_debut_prelevement = params$date_debut,
                 #date_fin_prelevement = params$date_fin,
                 size = params$pagination,
                 format = 'json')

# Requête des chroniques
hubeau_resultats <- get_hubeau(path = params$api_resultats, query=query_resultats)%>%union(hubeau_resultats)
Warning: call dbDisconnect() when finished working with a connection
Warning: call dbDisconnect() when finished working with a connection
Warning: call dbDisconnect() when finished working with a connection
# Conversion au format géographique
resultats <- st_as_sf(hubeau_resultats, coords = c("coordonnee_x", "coordonnee_y"), 
    crs = 2154, agr = "constant")

Import des référentiels géographiques

# couche des SAGEs bretons depuis Geobretagne
sages <- st_read("https://geobretagne.fr/geoserver/dreal_b/sage_dreal/wfs?SERVICE=WFS&REQUEST=GetCapabilities")
Reading layer `dreal_b:sage_dreal' from data source 
  `https://geobretagne.fr/geoserver/dreal_b/sage_dreal/wfs?SERVICE=WFS&REQUEST=GetCapabilities' using driver `WFS'
Simple feature collection with 21 features and 7 fields
Geometry type: MULTISURFACE
Dimension:     XY
Bounding box:  xmin: 123903.4 ymin: 6703536 xmax: 422117.4 ymax: 6882053
Projected CRS: RGF93 / Lambert-93
# pb pas moyen de manipuler cet objet => solution sur
# https://gis.stackexchange.com/questions/389814/r-st-centroid-geos-error-unknown-wkb-type-12/389854#389854
ensure_multipolygons <- function(X) {
  tmp1 <- tempfile(fileext = ".gpkg")
  tmp2 <- tempfile(fileext = ".gpkg")
  st_write(X, tmp1)
  gdalUtilities::ogr2ogr(tmp1, tmp2, f = "GPKG", nlt = "MULTIPOLYGON")
  Y <- st_read(tmp2)
  st_sf(st_drop_geometry(X), geom = st_geometry(Y))
}

sages <- ensure_multipolygons(sages)
Writing layer `file42dc70e711fa' to data source `C:\Users\tbesse\AppData\Local\Temp\RtmpIHxy8N\file42dc70e711fa.gpkg' using driver `GPKG'
Writing 21 features with 7 fields and geometry type Multi Surface.
Reading layer `file42dc70e711fa' from data source `C:\Users\tbesse\AppData\Local\Temp\RtmpIHxy8N\file42dc2c367dd1.gpkg' using driver `GPKG'
Simple feature collection with 21 features and 7 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: 123903.4 ymin: 6703536 xmax: 422117.4 ymax: 6882053
Projected CRS: RGF93 / Lambert-93
# couche des Hydroécorégions de niveau 2

her2 <- st_read("https://services.sandre.eaufrance.fr/geo/mdo?SERVICE=WFS&VERSION=2.0.0&REQUEST=GetFeature&typename=Hydroecoregion2")%>%
  st_transform(2154)
Reading layer `Hydroecoregion2' from data source 
  `https://services.sandre.eaufrance.fr/geo/mdo?SERVICE=WFS&VERSION=2.0.0&REQUEST=GetFeature&typename=Hydroecoregion2' 
  using driver `GML'
Simple feature collection with 114 features and 5 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -4.79495 ymin: 41.36882 xmax: 9.560037 ymax: 51.08965
Geodetic CRS:  WGS 84

Sélection des sites et des résultats sur les territoires des SAGE bretons


sites_sages <- sites %>% 
  st_join(sages) %>% 
  filter(!is.na(cd_sage))

resultats_sages <- resultats %>% 
  st_join(sages) %>% 
  filter(!is.na(cd_sage)) %>% 
  st_join(her2)

Les sites font-ils partie du réseau RCS ?

sites_rcs <- sites_sages %>%
  # rowwise pour préciser que les opérations se font pour chaque ligne
  rowwise()%>%
  # Le code 0000000052 (RCS) est dans la liste des codes_réseaux
  mutate(inclus_rcs = '0000000052' %in% unlist(codes_reseaux))%>%
  select(code_station_hydrobio, inclus_rcs)
sites_sages %>%
  ggplot()+
  geom_sf(data=sages)+
geom_sf(aes(color=libelle_departement))

resultats_sages %>%
  ggplot()+
  geom_sf(data=sages)+
geom_sf(aes(color=libelle_qualification))

resultats_sages %>%
  filter(code_indice == '5856')%>% #5856 IBD Indice Diatomées
  ggplot()+
  geom_sf(data=sages)+
geom_sf(aes(color=resultat_indice))

Jointure avec les tables référentiels

Sites inconnus

sites_inconnus <- sites_sages %>%
  # tables des sites
  left_join(tbl(con_postgresql_dev, dbplyr::in_schema("eau_structure","site")), by=c("code_station_hydrobio" = "code"), copy=TRUE, suffix = c("", ".site"))%>%
  filter(is.na(site_id))

sites_inconnus %>% 
  distinct(code_station_hydrobio, libelle_station_hydrobio)
sites_inconnus %>%
  ggplot()+
  geom_sf(data=sages)+
geom_sf(aes(color=libelle_departement))

MAJ sites inconnus


insert_sites <- sites_inconnus %>%
  mutate(coord_x = st_coordinates(st_transform(geometry, 2154))[,1],
         coord_y = st_coordinates(st_transform(geometry, 2154))[,2],
         longitude_wgs84 = st_coordinates(st_transform(geometry, 4326))[,1],
         latitude_wgs84 = st_coordinates(st_transform(geometry, 4326))[,2],
         typesite_id = '1',
         projection_id = '2154',
         source = 'OFB/NAIADES',
         maj = format(Sys.Date(),"%Y-%m-%d")
         )%>% 
  as_tibble() %>%
select(code = code_station_hydrobio,
libelle = libelle_station_hydrobio,
typesite_id,
coord_x,
coord_y,
projection_id,
longitude_wgs84,
latitude_wgs84,
source,
maj)

insert_sites
NA

Import de la table de correspondance site / EGA

correspondance_site_ega <- tbl(con_postgresql_dev, dbplyr::in_schema("eau_referentiel","geo_correspondance_site_ega"))%>%
  mutate(typesite = 'SITE')

correspondance_site_ega

liste_parametres <- as.list(strsplit(params$codes_parametres, ",")[[1]])

parametres <- tbl(con_postgresql_dev, dbplyr::in_schema("eau_structure","parametre"))%>%
  collect() %>%
  filter(code %in% liste_parametres)

parametres

classes_qualite <- tbl(con_postgresql_dev, dbplyr::in_schema("eau_structure","join_parametre_classe"))%>%
  left_join(tbl(con_postgresql_dev, dbplyr::in_schema("eau_structure","classe")), by="classe_id")%>%
  collect()%>%
  filter(parametre_id %in% parametres$parametre_id,
         valide == 1)

classes_qualite

Table complète

table_indices <- resultats_sages %>%
  mutate(CoordX_WGS84 = st_coordinates(st_transform(geometry, 4326))[,1],
         CoordY_WGS84 = st_coordinates(st_transform(geometry, 4326))[,2],
         date_prelevement = as.Date(date_prelevement)
         )%>%
  as_tibble()%>%
  # tables des paramètres
  left_join(parametres, by=c("code_indice"="code"), suffix = c("", ".parametre"))%>%
  # Unité inconnue --> code sandre 0
  # n (nombre) --> code sandre 214
  # ‰ vs SMOW --> code sandre 32
  mutate(unite_code = case_when(unite_indice == "Unité inconnue" ~ '0',
                                  unite_indice == "n" ~ '214',
                                  unite_indice == "‰ vs SMOW" ~ '32',
                                  TRUE ~ unite_indice),
         resultat_indice = ifelse(resultat_indice == 999, NA, resultat_indice)) %>%
  # tables des unités
  left_join(tbl(con_postgresql_dev, dbplyr::in_schema("eau_referentiel","unite")), by=c("unite_code" = "code"), copy=TRUE, suffix = c("", ".unite"))%>%
  # tables des sites
  left_join(tbl(con_postgresql_dev, dbplyr::in_schema("eau_structure","site")), by=c("code_station_hydrobio" = "code"), copy=TRUE, suffix = c("", ".site"))

table_indices 
# Synthèse des données manquantes
table_indices %>%
  summarise(
    nb_lignes = n(),
    lignes_sans_parametre = sum(is.na(parametre_id)),
    lignes_sans_unite = sum(is.na(unite_id)),
    lignes_sans_sites = sum(is.na(site_id))
  )

# Unités inconnues de la table de référence
table_indices %>% filter(is.na(unite_id)) %>% distinct(unite_indice)

# Stations inconnues de la table des sites
table_indices %>% filter(is.na(site_id)) %>% distinct(libelle_station_hydrobio)

# Liste des codes indices
table_indices %>% distinct(code_indice)
NA

Classes des indices

Exploration des données

table_indices_classes %>%
  group_by(year(date_prelevement), libelle_support, libelle_indice) %>%
  summarise(Nb_resultats = n(),
            Annee = year(date_prelevement)) %>%
  ggplot(aes(x = as.factor(Annee), y=Nb_resultats, fill=libelle_indice))+
           geom_bar(stat="identity")+
  facet_grid(libelle_support~.)
`summarise()` has grouped output by 'year(date_prelevement)', 'libelle_support', 'libelle_indice'. You can override using the `.groups` argument.

NA
table_indices_classes %>%
  arrange(code)%>%
  ggplot(aes(x = code, y=resultat_indice, fill = code))+
           geom_boxplot()+
  facet_wrap(~libelle_indice, scales = "free")

  
table_indices_classes

Transformation des données

Données annuelles


table_indices_classes_annee <- table_indices_classes %>%
  mutate(Annee = year(as.Date(date_prelevement))) %>%
  group_by(code_station_hydrobio,
           longitude_wgs84,
           latitude_wgs84,
           libelle_support,
           code_indice,
           parametre_id,
           libelle,
           symbole,
           Annee) %>%
  summarise(classe = as.integer(max(code)), 
            resultat_indice = mean(resultat_indice),
            resultat_qualification = as.integer(max(code_qualification)))
`summarise()` has grouped output by 'code_station_hydrobio', 'longitude_wgs84', 'latitude_wgs84', 'libelle_support', 'code_indice', 'parametre_id', 'libelle', 'symbole'. You can override using the `.groups` argument.
table_indices_classes_annee

Classe de qualité biologique globale (max des classes par indice)

table_indices_classe_globale_annee <- table_indices_classes_annee %>%
  group_by(code_station_hydrobio, longitude_wgs84, latitude_wgs84, Annee)%>%
  summarise(Resultat = max(classe))%>%
  mutate(Serie = 'Classe - Qualité biologique Globale', 
         libelle_support = 'Qualité biologique Globale',
         symbole = 'X')
`summarise()` has grouped output by 'code_station_hydrobio', 'longitude_wgs84', 'latitude_wgs84'. You can override using the `.groups` argument.
table_indices_classe_globale_annee

Import des données en base

Données au format de la table eau_structure.analyse_bio_esu - serveur PostgreSQL DEV

Mise en forme de la table

Sélection des nouvelles lignes à insérer

Insertion des nouvelles lignes

Données au format de la table eau_tbi.oeb_eau_qualite_biologique_ce - serveur MariaDB OEB


table_series_indices <- table_indices_classes_annee %>% 
  mutate(indice = paste(libelle, code_indice, sep=' - '))%>%
  pivot_longer(cols= c("classe","resultat_indice","resultat_qualification"))%>%
  mutate(Serie = case_when(name == 'classe' ~ paste('Classe',libelle_support,sep = ' - '),
                           name == 'resultat_indice' ~ paste('Indice',indice,sep = ' - '),
                           name == 'resultat_qualification' ~ paste('Qualification',indice,sep = ' - ')
                           )
         )%>%
  group_by(code_station_hydrobio,
           longitude_wgs84,
           latitude_wgs84,
           libelle_support,
           Serie,
           symbole,
           Annee)%>%
  summarise(Resultat = max(value))%>%
  ungroup()%>%
  union(table_indices_classe_globale_annee)
`summarise()` has grouped output by 'code_station_hydrobio', 'longitude_wgs84', 'latitude_wgs84', 'libelle_support', 'Serie', 'symbole'. You can override using the `.groups` argument.
table_series_indices

Déclinaison par combinaison SITE / EGA

table_series_indices_ega <- table_series_indices%>%
  # table des correspondances sites / UGA
  left_join(correspondance_site_ega, by=c("code_station_hydrobio" = "cdsite"), copy = TRUE)%>%
  left_join(sites_rcs, by="code_station_hydrobio")

table_series_indices_ega

Mise en forme de la table


oeb_eau_qualite_biologique_ce <- table_series_indices_ega %>%
  mutate(Periode = as.character(Annee),
         Source = 'OFB/NAIADES',
         Mise_a_jour = format(Sys.Date(),"%Y-%m-%d")
         )%>% 
  select(Type_entitite_geographique = typesite,
         Code_entitite_geographique = code_station_hydrobio,
         Libelle_entitite_geographique = lbsite,
         CoordX_WGS84 = longitude_wgs84,
         CoordY_WGS84 = latitude_wgs84,
         Reseau_RCS = inclus_rcs,
         Type_entitite_geographique_associee = typeega,
         Code_entitite_geographique_associee = cdega,
         Libelle_entitite_geographique_associee = lbega,
         Periode,
         Serie,
         unite = symbole,
         Resultat,
         Source,
         Mise_a_jour
  )

oeb_eau_qualite_biologique_ce
NA

Sélection des lignes nouvelles à insérer


insert_oeb_eau_qualite_biologique_ce <- oeb_eau_qualite_biologique_ce %>% 
  ungroup() %>%
  # Retirer les lignes des résultats déjà existants dans la table
  anti_join(tbl(con_eau_tbi,"oeb_eau_qualite_biologique_ce", by=c("Type_entitite_geographique", "Code_entitite_geographique", "Type_entitite_geographique_associee", "Code_entitite_geographique_associee", "Periode", "Serie")), copy = TRUE)%>%
  # Retirer les stations hydro sans EGA identifiée
  filter(!is.na(Type_entitite_geographique))%>%
  # Retirer les lignes avec une valeur NA
  drop_na()
Joining, by = c("Type_entitite_geographique", "Code_entitite_geographique", "Libelle_entitite_geographique", "CoordX_WGS84", "CoordY_WGS84", "Reseau_RCS", "Type_entitite_geographique_associee", "Code_entitite_geographique_associee", "Libelle_entitite_geographique_associee", "Periode", "Serie", "unite", "Resultat", "Source", "Mise_a_jour")
insert_oeb_eau_qualite_biologique_ce

Insertion des nouvelles lignes

Export pour le GIDE

# Depuis la base de données
#tbl(con_eau_tbi, "oeb_eau_qualite_biologique_ce_new")%>%
# ou depuis la table en mémoire
oeb_eau_qualite_biologique_ce %>%  
write.table(file = paste0(params$path_dataviz,"\\GIDE\\","oeb_eau_qualite_biologique_ce.csv"), quote = TRUE, sep = ";",
            eol = "\n", na = "", dec = ",",
            fileEncoding = "UTF-8")

Export pour GEOB

oeb_eau_qualite_geob <- table_series_indices  %>%
  ungroup()%>%
  left_join(select(sites_sages,code_station_hydrobio,libelle_station_hydrobio), by="code_station_hydrobio")  %>%
  mutate(Code_entitite_geographique = code_station_hydrobio,
         Libelle_entitite_geographique = libelle_station_hydrobio,
         CoordX_WGS84 = longitude_wgs84,
         CoordY_WGS84 = latitude_wgs84,
         Serie,
         unite = symbole,
         Type_entitite_geographique = 'SITE',
         Source = 'OFB/NAIADES',
         Mise_a_jour = format(Sys.Date(),"%Y-%m-%d")) %>%
  select(Type_entitite_geographique,
         Code_entitite_geographique,
         Libelle_entitite_geographique,
         CoordX_WGS84,
         CoordY_WGS84,
         Serie,
         unite,
         Source,
         Mise_a_jour,
         Annee,
         Resultat) %>%
  pivot_wider(values_from = Resultat, names_from = Annee, names_sort=TRUE)

oeb_eau_qualite_geob  %>%
  filter(Serie == 'Classe - Qualité biologique Globale')  %>%
write.table(file = paste0(params$path_dataviz,"\\GEOB\\","oeb_eau_qualite_biologique_globale.csv"), quote = TRUE, sep = ";",
            eol = "\n", na = "", dec = ",",
            fileEncoding = "UTF-8")

oeb_eau_qualite_geob %>%
  filter(Serie == 'Classe - Diatomées benthiques') %>%
write.table(file = paste0(params$path_dataviz,"\\GEOB\\","oeb_eau_qualite_diatomees.csv"), quote = TRUE, sep = ";",
            eol = "\n", na = "", dec = ",",
            fileEncoding = "UTF-8")

oeb_eau_qualite_geob %>%
  filter(Serie == 'Classe - Macroinvertébrés aquatiques') %>%
write.table(file = paste0(params$path_dataviz,"\\GEOB\\","oeb_eau_qualite_macroinvertebres.csv"), quote = TRUE, sep = ";",
            eol = "\n", na = "", dec = ",",
            fileEncoding = "UTF-8")

oeb_eau_qualite_geob %>%
  filter(Serie == 'Classe - Macrophytes') %>%
write.table(file = paste0(params$path_dataviz,"\\GEOB\\","oeb_eau_qualite_macrophytes.csv"), quote = TRUE, sep = ";",
            eol = "\n", na = "", dec = ",",
            fileEncoding = "UTF-8")
LS0tDQp0aXRsZTogIkludGVncmF0aW9uIGRlcyBkb25uw6llcyBoeWRyb2Jpb2xvZ2lxdWVzIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQpwYXJhbXM6DQogIGFwaV9zaXRlczogJ2h0dHBzOi8vaHViZWF1LmVhdWZyYW5jZS5mci9hcGkvdmJldGEvaHlkcm9iaW8vc3RhdGlvbnNfaHlkcm9iaW8/Jw0KICBhcGlfcmVzdWx0YXRzOiAnaHR0cHM6Ly9odWJlYXUuZWF1ZnJhbmNlLmZyL2FwaS92YmV0YS9oeWRyb2Jpby9pbmRpY2VzPycNCiAgbnVtX2RlcGFydGVtZW50OiAnMjIsMjksMzUsNTYsNTAsNDQsNDksNTMnICAjIDIyLDI5LDM1LDU2LDUwLDQ0LDQ5LDUzDQogIGNvZGVzX3BhcmFtZXRyZXM6ICc3MDM2LDU5MTAsMTAyMiw1ODU2LDI5MjgsNjk1OSw2OTUxLDY5NTUsMTAwMCwyNTI3JyAjIDcwMzYsNTkxMCwxMDIyLDU4NTYsMjkyOCw2OTU5LDY5NTEsNjk1NSwxMDAwLDI1MjcNCiAgZGF0ZV9kZWJ1dDogMjAxNy0wMS0wMQ0KICBkYXRlX2ZpbjogMjAxOS0xMi0zMQ0KICBwYWdpbmF0aW9uOiA1MDAwDQogIHBhdGhfZGF0YXZpejogJ086XDA0LkRBVEFWSVNVQUxJU0FUSU9OXElORElDQVRFVVJTX0JJT0xPR0lFXERDRV9FVEFUX0JJT0xPR0lRVUUnDQogIA0KLS0tDQoNCmBgYHtyIHNldHVwfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQ0KbGlicmFyeShjb25maWcpICMgVXRpbGlzYXRpb24gZCd1biBmaWNoaWVyIGRlIGNvbmZpZ3VyYXRpb24sIGNmICBodHRwczovL2RiLnJzdHVkaW8uY29tL2Jlc3QtcHJhY3RpY2VzL21hbmFnaW5nLWNyZWRlbnRpYWxzLyNzdG9yZWQtaW4tYS1maWxlLXdpdGgtY29uZmlnDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoREJJKSAjIHBvdXIgbGVzIGNvbm5leGlvbnMgYXV4IEJERA0KbGlicmFyeShSUG9zdGdyZVNRTCkgIyBkcml2ZXIgcG9zdGdyZXMNCmxpYnJhcnkoUk1hcmlhREIpICMgZHJpdmVyIE1hcmlhREINCmxpYnJhcnkoUk15U1FMKSAjIGRyaXZlciBNeVNRTA0KbGlicmFyeShsdWJyaWRhdGUpICMgY2FsY3VsIHN1ciBsZXMgZGF0ZXMNCg0KbGlicmFyeShodHRyKSAjDQpsaWJyYXJ5KGpzb25saXRlKSAjIEdlc3Rpb24gZm9ybWF0IGpzb24NCg0KbGlicmFyeSgicmVhZHhsIikgIyBMZWN0dXJlIGRlIGZpY2hpZXJzIHhsc3gNCg0KbGlicmFyeShzZikNCmxpYnJhcnkobWFwdmlldykNCmxpYnJhcnkocGxvdGx5KSAjIEdyYXBoaXF1ZXMgaW50ZXJhY3RpZnMNCmxpYnJhcnkobGVhZmxldCkgIyBDYXJ0ZXMgaW50ZXJhY3RpdmVzDQoNCiMgRm9uY3Rpb24gcG91ciB0cmFkdWlyZSBsYSByw6lwb25zZSBkZSBsJ0FQSQ0KYXBpX3JlcG9uc2UgPC0gZnVuY3Rpb24ocmVxdWVzdCkgew0KICANCmNhc2Vfd2hlbihyZXF1ZXN0JHN0YXR1c19jb2RlPT0yMDAgfiAiT0ssIHRvdXMgbGVzIHLDqXN1bHRhdHMgc29udCBwcsOpc2VudHMgZGFucyBsYSByw6lwb25zZSIsDQogICAgICAgICAgcmVxdWVzdCRzdGF0dXNfY29kZT09MjA2IH4gIk9LLCBpbCByZXN0ZSBkZXMgcsOpc3VsdGF0cyIsDQogICAgICAgICAgcmVxdWVzdCRzdGF0dXNfY29kZT09NDAwIH4gIlJlcXXDqnRlIGluY29ycmVjdGUiLA0KICAgICAgICAgICAgVFJVRSB+ICJBdXRyZSByw6lwb25zZSIpDQp9DQoNCiMgRm9uY3Rpb24gcG91ciByZXF1w6p0ZXIgbCdBUEkgSHViJ0VhdQ0KDQpnZXRfaHViZWF1IDwtIGZ1bmN0aW9uKHBhdGgsIHF1ZXJ5KSB7DQogIA0KICByZXNwb25zZSA8LSBHRVQodXJsID0gcGF0aCwgcXVlcnkgPSBxdWVyeSkgJT4lIA0KICBjb250ZW50KGFzID0gInRleHQiLCBlbmNvZGluZyA9ICJVVEYtOCIpICU+JQ0KICBmcm9tSlNPTihmbGF0dGVuID0gVFJVRSkNCiAgDQpkYXRhID0gcmVzcG9uc2UkZGF0YSAjIExlIGpldSBkZSBkb25uw6llcyBlc3QgZGFucyBsJ29iamV0ICJkYXRhIg0KDQppZihyZXNwb25zZSRjb3VudCA+IHF1ZXJ5JHNpemUpIHsgIyBTaSBsYSB0YWlsbGUgZGUgcGFnZSBlc3QgcGx1cyBwZXRpdGUgcXVlIGxlIG5iIGRlIHLDqXN1bHRhdHMsIGZhaXJlIHVuZSBib3VjbGUgcG91ciBsZXMgcGFnZXMgc3VpdmFudGVzDQogIA0KcGFnZXMgPC0gY2VpbGluZyhyZXNwb25zZSRjb3VudC9xdWVyeSRzaXplKQ0KDQojIEFmZmljaGFnZSBkJ3VuZSBiYXJyZSBkZSBwcm9ncmVzc2lvbg0KcGIgPC0gd2luUHJvZ3Jlc3NCYXIodGl0bGUgPSAiUsOpY3Vww6lyYXRpb24gZGVzIGNocm9uaXF1ZXMiLCBtaW4gPSAwLA0KICAgICAgICAgICAgICAgICAgICAgbWF4ID0gcGFnZXMsIHdpZHRoID0gMzAwKQ0KDQpmb3IoaSBpbiAyOnBhZ2VzKXsNCiAgDQogIHF1ZXJ5JHBhZ2UgPC0gaQ0KICANCiAgcmVzcG9uc2VfaSA8LSBHRVQodXJsID0gcGF0aCwgcXVlcnkgPSBxdWVyeSkgJT4lIA0KICBjb250ZW50KGFzID0gInRleHQiLCBlbmNvZGluZyA9ICJVVEYtOCIpICU+JQ0KICBmcm9tSlNPTihmbGF0dGVuID0gVFJVRSkNCg0KZGF0YSA8LSByYmluZChkYXRhLCByZXNwb25zZV9pJGRhdGEpICMgQ29uY2F0w6luYXRpb24gZGVzIGxpZ25lcyByw6ljdXDDqXLDqWVzDQoNCnNldFdpblByb2dyZXNzQmFyKHBiLCBpLCB0aXRsZT1wYXN0ZSggcm91bmQoaS9wYWdlcyoxMDAsIDApLCAiJSBjaGFyZ8OpIikpICMgQXZhbmNlbWVudCBkZSBsYSBiYXJyZSBkZSBwcm9ncmVzc2lvbg0KDQp9ICMgRmluIGJvdWNsZSBmb3INCg0KY2xvc2UocGIpDQoNCn0gIyBGaW4gaWYNCg0KcmV0dXJuKGRhdGEpDQogIH0gIyBGaW4gZ2V0X2h1YmVhdQ0KDQojIEluc2VydGlvbiBkZSBkb25uw6llcw0KDQpkYi5pbnNlcnRpb24gPC0gZnVuY3Rpb24oY29uLHRhYmxlLGRhdGEpIHsNCiAgDQogICMgRGF0ZSBkZSBsYSBkZXJuacOocmUgZG9ubsOpZSBlbiBiYXNlDQogIERhdGVfbWF4IDwtIHRibChjb24sIHRhYmxlKSAlPiUgc3VtbWFyaXNlKERhdGVfbWF4ID0gbWF4KERhdGVfZGVfbGFfbWVzdXJlLCBuYS5ybSA9IFRSVUUpKSAlPiUgcHVsbChEYXRlX21heCkNCiAgaWYoaXMubmEoRGF0ZV9tYXgpKSBEYXRlX21heCA8LSBhcy5EYXRlKHBhcmFtcyRkYXRlX2RlYnV0KS0xDQogIA0KICAjIERvbm7DqWVzIHBsdXMgcsOpY2VudGVzIMOgIGluc8OpcmVyDQogIGRhdGEgPC0gZGF0YSAlPiUgZmlsdGVyKERhdGVfZGVfbGFfbWVzdXJlID4gRGF0ZV9tYXgpDQogIA0KICAjIEV4aXN0ZS10LWlsIGRlcyBkb25uw6llcyDDoCBpbnPDqXJlciA/DQppZiAoaXNUUlVFKGRhdGEgJT4lIHRhbGx5KCkgPiAwKSkgew0KICANCiAgIyBJbnNlcnRpb24gZGVzIG5vdXZlbGxlcyBsaWduZXMNCiAgZGJXcml0ZVRhYmxlKGNvbiwgdGFibGUsIGRhdGEsIG92ZXJ3cml0ZT1GQUxTRSwgYXBwZW5kPVRSVUUsDQogICAgICAgICAgICAgZmlsZUVuY29kaW5nPSJsYXRpbjEiKQ0KICANCiAgIyBIb3JvZGF0ZSBlbiBjb21tZW50YWlyZQ0KbWlzZV9hX2pvdXIgPC0gcGFzdGUwKGZvcm1hdC5EYXRlKFN5cy5EYXRlKCksIiVkLyVtLyVZIiksICIgOiBBY3R1YWxpc2F0aW9uICIscGFyYW1zJGFjdHVhbGlzYXRpb24pDQoNCmRiR2V0UXVlcnkoY29uLCBwYXN0ZTAoIkFMVEVSIFRBQkxFICIsdGFibGUsIiBDT01NRU5UID0gJyIsbWlzZV9hX2pvdXIsIic7IikpDQoNCnBhc3RlMCgiRG9ubsOpZXMgaW5zw6lyw6llcyA6ICIsZGF0YSU+JXRhbGx5KQ0KDQp9DQogIGVsc2Ugew0KICAgIA0KICAgIHBhc3RlMCgiQXVjdW5lIGRvbm7DqWUgw6AgaW5zw6lyZXIgZGVwdWlzIGxlICIsIGZvcm1hdC5EYXRlKERhdGVfbWF4LCIlZC8lbS8lWSIpKQ0KICANCiAgICB9DQogIA0KfSAjIGZpbiBkYi5pbnNlcnRpb24NCmBgYA0KDQoNCmBgYHtyIGNvbm5leGlvbl9iZH0NCg0KIyBCYXNlIE1hcmlhZGIgT0VCDQpjb25fZWF1X3RiaSA8LSBkYkNvbm5lY3QoUk1hcmlhREI6Ok1hcmlhREIoKSwgZGVmYXVsdC5maWxlID0gJy4uLy4uLy5teS5jbmYnLCBncm91cHM9Im15c3FsX29lYiIsDQpkYm5hbWUgPSAiZWF1X3RiaSIpDQoNCiMgVmVyc2lvbiBsb2NhbGhvc3QNCmNvbl9vZWJfdGJpIDwtIGRiQ29ubmVjdChSTWFyaWFEQjo6TWFyaWFEQigpLCBkZWZhdWx0LmZpbGUgPSAnLi4vLi4vLm15LmNuZicsIGdyb3Vwcz0ibXlzcWxfbG9jYWwiLA0KZGJuYW1lID0gIm9lYl90YmkiKQ0KDQoNCmNvbl9yZWZlcmVudGllbHMgPC0gZGJDb25uZWN0KFJNYXJpYURCOjpNYXJpYURCKCksIGRlZmF1bHQuZmlsZSA9ICcuLi8uLi8ubXkuY25mJywgZ3JvdXBzPSJteXNxbF9vZWIiLA0KZGJuYW1lID0gImVhdV9yZWZlcmVudGllbHMiKQ0KDQoNCmRiTGlzdFRhYmxlcyhjb25fZWF1X3RiaSkgIyBMaXN0ZXIgbGVzIHRhYmxlcyBkZSBsYSBiYXNlDQoNCmNvbmYgPC0gY29uZmlnOjpnZXQoInBvc3RncmVzX2RldiIpDQoNCmNvbl9wb3N0Z3Jlc3FsX2RldiA8LSBEQkk6OmRiQ29ubmVjdChvZGJjOjpvZGJjKCksDQogICAgICAgICAgICAgICAgICAgICAgICAgIERyaXZlciAgICAgICA9IGNvbmYkZHJpdmVyLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBzZXJ2ZXJuYW1lICAgPSBjb25mJHNlcnZlciwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgVUlEID0gY29uZiR1aWQsDQogICAgICAgICAgICAgICAgICAgICAgICAgIFBXRCA9IGNvbmYkcHdkLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBQb3J0ID0gY29uZiRwb3J0LA0KICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhYmFzZSA9ICdlYXUnLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBlbmNvZGluZyA9ICJsYXRpbjEiKQ0KYGBgDQoNCmBgYHtyIGh1YmVhdX0NCg0KIyBSw6ljdXDDqXJhdGlvbiBkZXMgaW5mb3Mgc3VyIGxlcyBzdGF0aW9ucyBkZSBtZXN1cmUNCg0KIyBMaXN0ZSBkZXMgcGFyYW3DqHRyZXMgZGUgbGEgcmVxdcOqdGUNCnF1ZXJ5X3NpdGVzID0gbGlzdCgNCiAgY29kZV9kZXBhcnRlbWVudD0gcGFyYW1zJG51bV9kZXBhcnRlbWVudCwNCiAgZm9ybWF0PSdqc29uJywNCiAgc2l6ZT1wYXJhbXMkcGFnaW5hdGlvbg0KICApDQoNCiMgUmVxdcOqdGUgZGVzIHN0YXRpb25zDQpodWJlYXVfc2l0ZXMgPC0gZ2V0X2h1YmVhdShwYXRoID0gcGFyYW1zJGFwaV9zaXRlcywgcXVlcnkgPSBxdWVyeV9zaXRlcykNCg0KIyBDb252ZXJzaW9uIGF1IGZvcm1hdCBnw6lvZ3JhcGhpcXVlDQpzaXRlcyA8LSBzdF9hc19zZihodWJlYXVfc2l0ZXMsIGNvb3JkcyA9IGMoImNvb3Jkb25uZWVfeCIsICJjb29yZG9ubmVlX3kiKSwgDQogICAgY3JzID0gMjE1NCwgYWdyID0gImNvbnN0YW50IikNCg0KIyBSw6ljdXDDqXJhdGlvbiBkZXMgcsOpc3VsdGF0cyBkJ2luZGljZXMgYmlvbG9naXF1ZXMNCg0KIyBMaXN0ZSBkZXMgcGFyYW3DqHRyZXMgZGUgbGEgcmVxdcOqdGUgKGxvdCAxIGRlIGTDqXBhcnRlbWVudHMpDQpxdWVyeV9yZXN1bHRhdHMgPSBsaXN0KA0KICAgICAgICAgICAgICAgICAjY29kZV9kZXBhcnRlbWVudCA9IHBhcmFtcyRudW1fZGVwYXJ0ZW1lbnQsDQogICAgICAgICAgICAgICAgIGNvZGVfZGVwYXJ0ZW1lbnQgPSAiMjksMjIsMzUiLA0KICAgICAgICAgICAgICAgICBjb2RlX2luZGljZSA9IHBhcmFtcyRjb2Rlc19wYXJhbWV0cmVzLA0KICAgICAgICAgICAgICAgICAjbGVzIHBhcmFtw6h0cmVzIGRhdGVfZGVidXQgZXQgZGF0ZV9maW4gY3LDqWVudCB1biBidWcgZGFucyBsJ0FQSSBodHRwczovL2dpdGh1Yi5jb20vQlJHTS9odWJlYXUvaXNzdWVzLzgxDQogICAgICAgICAgICAgICAgICNkYXRlX2RlYnV0X3ByZWxldmVtZW50ID0gcGFyYW1zJGRhdGVfZGVidXQsDQogICAgICAgICAgICAgICAgICNkYXRlX2Zpbl9wcmVsZXZlbWVudCA9IHBhcmFtcyRkYXRlX2ZpbiwNCiAgICAgICAgICAgICAgICAgc2l6ZSA9IHBhcmFtcyRwYWdpbmF0aW9uLA0KICAgICAgICAgICAgICAgICBmb3JtYXQgPSAnanNvbicpDQoNCiMgUmVxdcOqdGUgZGVzIGNocm9uaXF1ZXMNCmh1YmVhdV9yZXN1bHRhdHMgPC0gZ2V0X2h1YmVhdShwYXRoID0gcGFyYW1zJGFwaV9yZXN1bHRhdHMsIHF1ZXJ5PXF1ZXJ5X3Jlc3VsdGF0cykNCg0KIyBMaXN0ZSBkZXMgcGFyYW3DqHRyZXMgZGUgbGEgcmVxdcOqdGUgKGxvdCAyIGRlIGTDqXBhcnRlbWVudHMpDQpxdWVyeV9yZXN1bHRhdHMgPSBsaXN0KA0KICAgICAgICAgICAgICAgICAjY29kZV9kZXBhcnRlbWVudCA9IHBhcmFtcyRudW1fZGVwYXJ0ZW1lbnQsDQogICAgICAgICAgICAgICAgIGNvZGVfZGVwYXJ0ZW1lbnQgPSAiNTYsNTAsNDQsNDksNTMiLA0KICAgICAgICAgICAgICAgICBjb2RlX2luZGljZSA9IHBhcmFtcyRjb2Rlc19wYXJhbWV0cmVzLA0KICAgICAgICAgICAgICAgICAjbGVzIHBhcmFtw6h0cmVzIGRhdGVfZGVidXQgZXQgZGF0ZV9maW4gY3LDqWVudCB1biBidWcgZGFucyBsJ0FQSSBodHRwczovL2dpdGh1Yi5jb20vQlJHTS9odWJlYXUvaXNzdWVzLzgxDQogICAgICAgICAgICAgICAgICNkYXRlX2RlYnV0X3ByZWxldmVtZW50ID0gcGFyYW1zJGRhdGVfZGVidXQsDQogICAgICAgICAgICAgICAgICNkYXRlX2Zpbl9wcmVsZXZlbWVudCA9IHBhcmFtcyRkYXRlX2ZpbiwNCiAgICAgICAgICAgICAgICAgc2l6ZSA9IHBhcmFtcyRwYWdpbmF0aW9uLA0KICAgICAgICAgICAgICAgICBmb3JtYXQgPSAnanNvbicpDQoNCiMgUmVxdcOqdGUgZGVzIGNocm9uaXF1ZXMNCmh1YmVhdV9yZXN1bHRhdHMgPC0gZ2V0X2h1YmVhdShwYXRoID0gcGFyYW1zJGFwaV9yZXN1bHRhdHMsIHF1ZXJ5PXF1ZXJ5X3Jlc3VsdGF0cyklPiV1bmlvbihodWJlYXVfcmVzdWx0YXRzKQ0KDQojIENvbnZlcnNpb24gYXUgZm9ybWF0IGfDqW9ncmFwaGlxdWUNCnJlc3VsdGF0cyA8LSBzdF9hc19zZihodWJlYXVfcmVzdWx0YXRzLCBjb29yZHMgPSBjKCJjb29yZG9ubmVlX3giLCAiY29vcmRvbm5lZV95IiksIA0KICAgIGNycyA9IDIxNTQsIGFnciA9ICJjb25zdGFudCIpDQpgYGANCg0KIyBJbXBvcnQgZGVzIHLDqWbDqXJlbnRpZWxzIGfDqW9ncmFwaGlxdWVzDQoNCmBgYHtyIGNvdWNoZV9zYWdlc30NCiMgY291Y2hlIGRlcyBTQUdFcyBicmV0b25zIGRlcHVpcyBHZW9icmV0YWduZQ0Kc2FnZXMgPC0gc3RfcmVhZCgiaHR0cHM6Ly9nZW9icmV0YWduZS5mci9nZW9zZXJ2ZXIvZHJlYWxfYi9zYWdlX2RyZWFsL3dmcz9TRVJWSUNFPVdGUyZSRVFVRVNUPUdldENhcGFiaWxpdGllcyIpDQoNCiMgcGIgcGFzIG1veWVuIGRlIG1hbmlwdWxlciBjZXQgb2JqZXQgPT4gc29sdXRpb24gc3VyDQojIGh0dHBzOi8vZ2lzLnN0YWNrZXhjaGFuZ2UuY29tL3F1ZXN0aW9ucy8zODk4MTQvci1zdC1jZW50cm9pZC1nZW9zLWVycm9yLXVua25vd24td2tiLXR5cGUtMTIvMzg5ODU0IzM4OTg1NA0KZW5zdXJlX211bHRpcG9seWdvbnMgPC0gZnVuY3Rpb24oWCkgew0KICB0bXAxIDwtIHRlbXBmaWxlKGZpbGVleHQgPSAiLmdwa2ciKQ0KICB0bXAyIDwtIHRlbXBmaWxlKGZpbGVleHQgPSAiLmdwa2ciKQ0KICBzdF93cml0ZShYLCB0bXAxKQ0KICBnZGFsVXRpbGl0aWVzOjpvZ3Iyb2dyKHRtcDEsIHRtcDIsIGYgPSAiR1BLRyIsIG5sdCA9ICJNVUxUSVBPTFlHT04iKQ0KICBZIDwtIHN0X3JlYWQodG1wMikNCiAgc3Rfc2Yoc3RfZHJvcF9nZW9tZXRyeShYKSwgZ2VvbSA9IHN0X2dlb21ldHJ5KFkpKQ0KfQ0KDQpzYWdlcyA8LSBlbnN1cmVfbXVsdGlwb2x5Z29ucyhzYWdlcykNCg0KIyBjb3VjaGUgZGVzIEh5ZHJvw6ljb3LDqWdpb25zIGRlIG5pdmVhdSAyDQoNCmhlcjIgPC0gc3RfcmVhZCgiaHR0cHM6Ly9zZXJ2aWNlcy5zYW5kcmUuZWF1ZnJhbmNlLmZyL2dlby9tZG8/U0VSVklDRT1XRlMmVkVSU0lPTj0yLjAuMCZSRVFVRVNUPUdldEZlYXR1cmUmdHlwZW5hbWU9SHlkcm9lY29yZWdpb24yIiklPiUNCiAgc3RfdHJhbnNmb3JtKDIxNTQpDQpgYGANCiMgU8OpbGVjdGlvbiBkZXMgc2l0ZXMgZXQgZGVzIHLDqXN1bHRhdHMgc3VyIGxlcyB0ZXJyaXRvaXJlcyBkZXMgU0FHRSBicmV0b25zDQoNCmBgYHtyIHNlbGVjdGlvbiBzdXIgbGVzIHRlcnJpdG9pcmVzIGRlcyBTQUdFc30NCg0Kc2l0ZXNfc2FnZXMgPC0gc2l0ZXMgJT4lIA0KICBzdF9qb2luKHNhZ2VzKSAlPiUgDQogIGZpbHRlcighaXMubmEoY2Rfc2FnZSkpDQoNCnJlc3VsdGF0c19zYWdlcyA8LSByZXN1bHRhdHMgJT4lIA0KICBzdF9qb2luKHNhZ2VzKSAlPiUgDQogIGZpbHRlcighaXMubmEoY2Rfc2FnZSkpICU+JSANCiAgc3Rfam9pbihoZXIyKQ0KDQpgYGANCg0KIyMgTGVzIHNpdGVzIGZvbnQtaWxzIHBhcnRpZSBkdSByw6lzZWF1IFJDUyA/DQoNCmBgYHtyIHNpdGVzX3Jjc30NCnNpdGVzX3JjcyA8LSBzaXRlc19zYWdlcyAlPiUNCiAgIyByb3d3aXNlIHBvdXIgcHLDqWNpc2VyIHF1ZSBsZXMgb3DDqXJhdGlvbnMgc2UgZm9udCBwb3VyIGNoYXF1ZSBsaWduZQ0KICByb3d3aXNlKCklPiUNCiAgIyBMZSBjb2RlIDAwMDAwMDAwNTIgKFJDUykgZXN0IGRhbnMgbGEgbGlzdGUgZGVzIGNvZGVzX3LDqXNlYXV4DQogIG11dGF0ZShpbmNsdXNfcmNzID0gJzAwMDAwMDAwNTInICVpbiUgdW5saXN0KGNvZGVzX3Jlc2VhdXgpKSU+JQ0KICBzZWxlY3QoY29kZV9zdGF0aW9uX2h5ZHJvYmlvLCBpbmNsdXNfcmNzKQ0KYGBgDQoNCmBgYHtyIGNhcnRlX3NpdGVzfQ0Kc2l0ZXNfc2FnZXMgJT4lDQogIGdncGxvdCgpKw0KICBnZW9tX3NmKGRhdGE9c2FnZXMpKw0KZ2VvbV9zZihhZXMoY29sb3I9bGliZWxsZV9kZXBhcnRlbWVudCkpDQpgYGANCg0KYGBge3IgY2FydGVfcmVzdWx0YXRzfQ0KcmVzdWx0YXRzX3NhZ2VzICU+JQ0KICBnZ3Bsb3QoKSsNCiAgZ2VvbV9zZihkYXRhPXNhZ2VzKSsNCmdlb21fc2YoYWVzKGNvbG9yPWxpYmVsbGVfcXVhbGlmaWNhdGlvbikpDQpgYGANCg0KYGBge3IgY2FydGVfaW5kaWNlfQ0KcmVzdWx0YXRzX3NhZ2VzICU+JQ0KICBmaWx0ZXIoY29kZV9pbmRpY2UgPT0gJzU4NTYnKSU+JSAjNTg1NiBJQkQgSW5kaWNlIERpYXRvbcOpZXMNCiAgZ2dwbG90KCkrDQogIGdlb21fc2YoZGF0YT1zYWdlcykrDQpnZW9tX3NmKGFlcyhjb2xvcj1yZXN1bHRhdF9pbmRpY2UpKQ0KYGBgDQoNCiMgSm9pbnR1cmUgYXZlYyBsZXMgdGFibGVzIHLDqWbDqXJlbnRpZWxzDQoNCiMjIFNpdGVzIGluY29ubnVzDQoNCmBgYHtyIHNpdGVzX2luY29ubnVzfQ0Kc2l0ZXNfaW5jb25udXMgPC0gc2l0ZXNfc2FnZXMgJT4lDQogICMgdGFibGVzIGRlcyBzaXRlcw0KICBsZWZ0X2pvaW4odGJsKGNvbl9wb3N0Z3Jlc3FsX2RldiwgZGJwbHlyOjppbl9zY2hlbWEoImVhdV9zdHJ1Y3R1cmUiLCJzaXRlIikpLCBieT1jKCJjb2RlX3N0YXRpb25faHlkcm9iaW8iID0gImNvZGUiKSwgY29weT1UUlVFLCBzdWZmaXggPSBjKCIiLCAiLnNpdGUiKSklPiUNCiAgZmlsdGVyKGlzLm5hKHNpdGVfaWQpKQ0KDQpzaXRlc19pbmNvbm51cyAlPiUgDQogIGRpc3RpbmN0KGNvZGVfc3RhdGlvbl9oeWRyb2JpbywgbGliZWxsZV9zdGF0aW9uX2h5ZHJvYmlvKQ0KYGBgDQoNCmBgYHtyIGNhcnRlX3NpdGVzX2luY29ubnVzfQ0Kc2l0ZXNfaW5jb25udXMgJT4lDQogIGdncGxvdCgpKw0KICBnZW9tX3NmKGRhdGE9c2FnZXMpKw0KZ2VvbV9zZihhZXMoY29sb3I9bGliZWxsZV9kZXBhcnRlbWVudCkpDQpgYGANCg0KIyMgTUFKIHNpdGVzIGluY29ubnVzDQoNCmBgYHtyIHRhYmxlIHNpdGVzX2luY29ubnVzfQ0KDQppbnNlcnRfc2l0ZXMgPC0gc2l0ZXNfaW5jb25udXMgJT4lDQogIG11dGF0ZShjb29yZF94ID0gc3RfY29vcmRpbmF0ZXMoc3RfdHJhbnNmb3JtKGdlb21ldHJ5LCAyMTU0KSlbLDFdLA0KICAgICAgICAgY29vcmRfeSA9IHN0X2Nvb3JkaW5hdGVzKHN0X3RyYW5zZm9ybShnZW9tZXRyeSwgMjE1NCkpWywyXSwNCiAgICAgICAgIGxvbmdpdHVkZV93Z3M4NCA9IHN0X2Nvb3JkaW5hdGVzKHN0X3RyYW5zZm9ybShnZW9tZXRyeSwgNDMyNikpWywxXSwNCiAgICAgICAgIGxhdGl0dWRlX3dnczg0ID0gc3RfY29vcmRpbmF0ZXMoc3RfdHJhbnNmb3JtKGdlb21ldHJ5LCA0MzI2KSlbLDJdLA0KICAgICAgICAgdHlwZXNpdGVfaWQgPSAnMScsDQogICAgICAgICBwcm9qZWN0aW9uX2lkID0gJzIxNTQnLA0KICAgICAgICAgc291cmNlID0gJ09GQi9OQUlBREVTJywNCiAgICAgICAgIG1haiA9IGZvcm1hdChTeXMuRGF0ZSgpLCIlWS0lbS0lZCIpDQogICAgICAgICApJT4lIA0KICBhc190aWJibGUoKSAlPiUNCnNlbGVjdChjb2RlID0gY29kZV9zdGF0aW9uX2h5ZHJvYmlvLA0KbGliZWxsZSA9IGxpYmVsbGVfc3RhdGlvbl9oeWRyb2JpbywNCnR5cGVzaXRlX2lkLA0KY29vcmRfeCwNCmNvb3JkX3ksDQpwcm9qZWN0aW9uX2lkLA0KbG9uZ2l0dWRlX3dnczg0LA0KbGF0aXR1ZGVfd2dzODQsDQpzb3VyY2UsDQptYWopDQoNCmluc2VydF9zaXRlcw0KDQpgYGANCg0KYGBge3IgaW5zZXJ0IHNpdGVzX2luY29ubnVzLCBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0KDQpzZjo6ZGJXcml0ZVRhYmxlKGNvbm4gPSBjb25fcG9zdGdyZXNxbF9kZXYsIG5hbWUgPSBJZChzY2hlbWEgPSAiZWF1X3N0cnVjdHVyZSIsdGFibGUgPSAic2l0ZSIpLCB2YWx1ZSA9IGluc2VydF9zaXRlcywgb3ZlcndyaXRlPUZBTFNFLCBhcHBlbmQ9VFJVRSwgZmlsZUVuY29kaW5nPSJsYXRpbjEiKQ0KYGBgDQoNCiMjIEltcG9ydCBkZSBsYSB0YWJsZSBkZSBjb3JyZXNwb25kYW5jZSBzaXRlIC8gRUdBDQoNCmBgYHtyIGltcG9ydF9lYXVfY29ycmVzcG9uZGFuY2Vfc2l0ZV9lZ2F9DQpjb3JyZXNwb25kYW5jZV9zaXRlX2VnYSA8LSB0YmwoY29uX3Bvc3RncmVzcWxfZGV2LCBkYnBseXI6OmluX3NjaGVtYSgiZWF1X3JlZmVyZW50aWVsIiwiZ2VvX2NvcnJlc3BvbmRhbmNlX3NpdGVfZWdhIikpJT4lDQogIG11dGF0ZSh0eXBlc2l0ZSA9ICdTSVRFJykNCg0KY29ycmVzcG9uZGFuY2Vfc2l0ZV9lZ2ENCmBgYA0KDQpgYGB7ciBpbXBvcnQgcGFyYW1ldHJlc30NCg0KbGlzdGVfcGFyYW1ldHJlcyA8LSBhcy5saXN0KHN0cnNwbGl0KHBhcmFtcyRjb2Rlc19wYXJhbWV0cmVzLCAiLCIpW1sxXV0pDQoNCnBhcmFtZXRyZXMgPC0gdGJsKGNvbl9wb3N0Z3Jlc3FsX2RldiwgZGJwbHlyOjppbl9zY2hlbWEoImVhdV9zdHJ1Y3R1cmUiLCJwYXJhbWV0cmUiKSklPiUNCiAgY29sbGVjdCgpICU+JQ0KICBmaWx0ZXIoY29kZSAlaW4lIGxpc3RlX3BhcmFtZXRyZXMpDQoNCnBhcmFtZXRyZXMNCmBgYA0KDQpgYGB7ciBpbXBvcnQgc2V1aWxzIGRlIHF1YWxpdMOpfQ0KDQpjbGFzc2VzX3F1YWxpdGUgPC0gdGJsKGNvbl9wb3N0Z3Jlc3FsX2RldiwgZGJwbHlyOjppbl9zY2hlbWEoImVhdV9zdHJ1Y3R1cmUiLCJqb2luX3BhcmFtZXRyZV9jbGFzc2UiKSklPiUNCiAgbGVmdF9qb2luKHRibChjb25fcG9zdGdyZXNxbF9kZXYsIGRicGx5cjo6aW5fc2NoZW1hKCJlYXVfc3RydWN0dXJlIiwiY2xhc3NlIikpLCBieT0iY2xhc3NlX2lkIiklPiUNCiAgY29sbGVjdCgpJT4lDQogIGZpbHRlcihwYXJhbWV0cmVfaWQgJWluJSBwYXJhbWV0cmVzJHBhcmFtZXRyZV9pZCwNCiAgICAgICAgIHZhbGlkZSA9PSAxKQ0KDQpjbGFzc2VzX3F1YWxpdGUNCmBgYA0KDQojIyBUYWJsZSBjb21wbMOodGUNCg0KYGBge3IgdGFibGUgaW5kaWNlc30NCnRhYmxlX2luZGljZXMgPC0gcmVzdWx0YXRzX3NhZ2VzICU+JQ0KICBtdXRhdGUoQ29vcmRYX1dHUzg0ID0gc3RfY29vcmRpbmF0ZXMoc3RfdHJhbnNmb3JtKGdlb21ldHJ5LCA0MzI2KSlbLDFdLA0KICAgICAgICAgQ29vcmRZX1dHUzg0ID0gc3RfY29vcmRpbmF0ZXMoc3RfdHJhbnNmb3JtKGdlb21ldHJ5LCA0MzI2KSlbLDJdLA0KICAgICAgICAgZGF0ZV9wcmVsZXZlbWVudCA9IGFzLkRhdGUoZGF0ZV9wcmVsZXZlbWVudCkNCiAgICAgICAgICklPiUNCiAgYXNfdGliYmxlKCklPiUNCiAgIyB0YWJsZXMgZGVzIHBhcmFtw6h0cmVzDQogIGxlZnRfam9pbihwYXJhbWV0cmVzLCBieT1jKCJjb2RlX2luZGljZSI9ImNvZGUiKSwgc3VmZml4ID0gYygiIiwgIi5wYXJhbWV0cmUiKSklPiUNCiAgIyBVbml0w6kgaW5jb25udWUgLS0+IGNvZGUgc2FuZHJlIDANCiAgIyBuIChub21icmUpIC0tPiBjb2RlIHNhbmRyZSAyMTQNCiAgIyDigLAgdnMgU01PVyAtLT4gY29kZSBzYW5kcmUgMzINCiAgbXV0YXRlKHVuaXRlX2NvZGUgPSBjYXNlX3doZW4odW5pdGVfaW5kaWNlID09ICJVbml0w6kgaW5jb25udWUiIH4gJzAnLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHVuaXRlX2luZGljZSA9PSAibiIgfiAnMjE0JywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB1bml0ZV9pbmRpY2UgPT0gIuKAsCB2cyBTTU9XIiB+ICczMicsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVFJVRSB+IHVuaXRlX2luZGljZSksDQogICAgICAgICByZXN1bHRhdF9pbmRpY2UgPSBpZmVsc2UocmVzdWx0YXRfaW5kaWNlID09IDk5OSwgTkEsIHJlc3VsdGF0X2luZGljZSkpICU+JQ0KICAjIHRhYmxlcyBkZXMgdW5pdMOpcw0KICBsZWZ0X2pvaW4odGJsKGNvbl9wb3N0Z3Jlc3FsX2RldiwgZGJwbHlyOjppbl9zY2hlbWEoImVhdV9yZWZlcmVudGllbCIsInVuaXRlIikpLCBieT1jKCJ1bml0ZV9jb2RlIiA9ICJjb2RlIiksIGNvcHk9VFJVRSwgc3VmZml4ID0gYygiIiwgIi51bml0ZSIpKSU+JQ0KICAjIHRhYmxlcyBkZXMgc2l0ZXMNCiAgbGVmdF9qb2luKHRibChjb25fcG9zdGdyZXNxbF9kZXYsIGRicGx5cjo6aW5fc2NoZW1hKCJlYXVfc3RydWN0dXJlIiwic2l0ZSIpKSwgYnk9YygiY29kZV9zdGF0aW9uX2h5ZHJvYmlvIiA9ICJjb2RlIiksIGNvcHk9VFJVRSwgc3VmZml4ID0gYygiIiwgIi5zaXRlIikpDQoNCnRhYmxlX2luZGljZXMgDQpgYGANCg0KYGBge3IgZG9ubmVlc19tYW5xdWFudGVzfQ0KIyBTeW50aMOoc2UgZGVzIGRvbm7DqWVzIG1hbnF1YW50ZXMNCnRhYmxlX2luZGljZXMgJT4lDQogIHN1bW1hcmlzZSgNCiAgICBuYl9saWduZXMgPSBuKCksDQogICAgbGlnbmVzX3NhbnNfcGFyYW1ldHJlID0gc3VtKGlzLm5hKHBhcmFtZXRyZV9pZCkpLA0KICAgIGxpZ25lc19zYW5zX3VuaXRlID0gc3VtKGlzLm5hKHVuaXRlX2lkKSksDQogICAgbGlnbmVzX3NhbnNfc2l0ZXMgPSBzdW0oaXMubmEoc2l0ZV9pZCkpDQogICkNCg0KIyBVbml0w6lzIGluY29ubnVlcyBkZSBsYSB0YWJsZSBkZSByw6lmw6lyZW5jZQ0KdGFibGVfaW5kaWNlcyAlPiUgZmlsdGVyKGlzLm5hKHVuaXRlX2lkKSkgJT4lIGRpc3RpbmN0KHVuaXRlX2luZGljZSkNCg0KIyBTdGF0aW9ucyBpbmNvbm51ZXMgZGUgbGEgdGFibGUgZGVzIHNpdGVzDQp0YWJsZV9pbmRpY2VzICU+JSBmaWx0ZXIoaXMubmEoc2l0ZV9pZCkpICU+JSBkaXN0aW5jdChsaWJlbGxlX3N0YXRpb25faHlkcm9iaW8pDQoNCiMgTGlzdGUgZGVzIGNvZGVzIGluZGljZXMNCnRhYmxlX2luZGljZXMgJT4lIGRpc3RpbmN0KGNvZGVfaW5kaWNlKQ0KDQpgYGANCg0KDQojIyBDbGFzc2VzIGRlcyBpbmRpY2VzDQoNCmBgYHtyIGNsYXNzZV9pbmRpY2VzfQ0KdGFibGVfaW5kaWNlc19jbGFzc2VzIDwtIHRhYmxlX2luZGljZXMgJT4lIA0KICBsZWZ0X2pvaW4oY2xhc3Nlc19xdWFsaXRlLCBieSA9IGMoInBhcmFtZXRyZV9pZCIpLCBjb3B5ID0gVFJVRSwgc3VmZml4PWMoIiIsIi5jbGFzc2UiKSkgJT4lDQogIGZpbHRlcihyZXN1bHRhdF9pbmRpY2UgPCBib3JuZV9zdXBfZXhjbHVlICYgcmVzdWx0YXRfaW5kaWNlID49IGJvcm5lX2luZl9pbmNsdWUpDQoNCnRhYmxlX2luZGljZXNfY2xhc3Nlcw0KYGBgDQoNCiMgRXhwbG9yYXRpb24gZGVzIGRvbm7DqWVzDQoNCmBgYHtyIGdyYXBoZV9pbmRpY2VzLCBmaWcuaGVpZ2h0PTgsIGZpZy53aWR0aD0xNn0NCnRhYmxlX2luZGljZXNfY2xhc3NlcyAlPiUNCiAgZ3JvdXBfYnkoeWVhcihkYXRlX3ByZWxldmVtZW50KSwgbGliZWxsZV9zdXBwb3J0LCBsaWJlbGxlX2luZGljZSkgJT4lDQogIHN1bW1hcmlzZShOYl9yZXN1bHRhdHMgPSBuKCksDQogICAgICAgICAgICBBbm5lZSA9IHllYXIoZGF0ZV9wcmVsZXZlbWVudCkpICU+JQ0KICBnZ3Bsb3QoYWVzKHggPSBhcy5mYWN0b3IoQW5uZWUpLCB5PU5iX3Jlc3VsdGF0cywgZmlsbD1saWJlbGxlX2luZGljZSkpKw0KICAgICAgICAgICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIpKw0KICBmYWNldF9ncmlkKGxpYmVsbGVfc3VwcG9ydH4uKQ0KICANCmBgYA0KDQpgYGB7ciBncmFwaGVfaW5kaWNlc19jbGFzc2VzLCBmaWcuaGVpZ2h0PTgsIGZpZy53aWR0aD0xNn0NCnRhYmxlX2luZGljZXNfY2xhc3NlcyAlPiUNCiAgYXJyYW5nZShjb2RlKSU+JQ0KICBnZ3Bsb3QoYWVzKHggPSBjb2RlLCB5PXJlc3VsdGF0X2luZGljZSwgZmlsbCA9IGNvZGUpKSsNCiAgICAgICAgICAgZ2VvbV9ib3hwbG90KCkrDQogIGZhY2V0X3dyYXAofmxpYmVsbGVfaW5kaWNlLCBzY2FsZXMgPSAiZnJlZSIpDQogIA0KdGFibGVfaW5kaWNlc19jbGFzc2VzDQpgYGANCg0KDQojIFRyYW5zZm9ybWF0aW9uIGRlcyBkb25uw6llcw0KDQojIyBEb25uw6llcyBhbm51ZWxsZXMNCg0KYGBge3IgdGFibGVfaW5kaWNlc19jbGFzc2VzX2FubmVlfQ0KDQp0YWJsZV9pbmRpY2VzX2NsYXNzZXNfYW5uZWUgPC0gdGFibGVfaW5kaWNlc19jbGFzc2VzICU+JQ0KICBtdXRhdGUoQW5uZWUgPSB5ZWFyKGFzLkRhdGUoZGF0ZV9wcmVsZXZlbWVudCkpKSAlPiUNCiAgZ3JvdXBfYnkoY29kZV9zdGF0aW9uX2h5ZHJvYmlvLA0KICAgICAgICAgICBsb25naXR1ZGVfd2dzODQsDQogICAgICAgICAgIGxhdGl0dWRlX3dnczg0LA0KICAgICAgICAgICBsaWJlbGxlX3N1cHBvcnQsDQogICAgICAgICAgIGNvZGVfaW5kaWNlLA0KICAgICAgICAgICBwYXJhbWV0cmVfaWQsDQogICAgICAgICAgIGxpYmVsbGUsDQogICAgICAgICAgIHN5bWJvbGUsDQogICAgICAgICAgIEFubmVlKSAlPiUNCiAgc3VtbWFyaXNlKGNsYXNzZSA9IGFzLmludGVnZXIobWF4KGNvZGUpKSwgDQogICAgICAgICAgICByZXN1bHRhdF9pbmRpY2UgPSBtZWFuKHJlc3VsdGF0X2luZGljZSksDQogICAgICAgICAgICByZXN1bHRhdF9xdWFsaWZpY2F0aW9uID0gYXMuaW50ZWdlcihtYXgoY29kZV9xdWFsaWZpY2F0aW9uKSkpDQoNCnRhYmxlX2luZGljZXNfY2xhc3Nlc19hbm5lZQ0KYGBgDQoNCiMjIENsYXNzZSBkZSBxdWFsaXTDqSBiaW9sb2dpcXVlIGdsb2JhbGUgKG1heCBkZXMgY2xhc3NlcyBwYXIgaW5kaWNlKQ0KDQpgYGB7cn0NCnRhYmxlX2luZGljZXNfY2xhc3NlX2dsb2JhbGVfYW5uZWUgPC0gdGFibGVfaW5kaWNlc19jbGFzc2VzX2FubmVlICU+JQ0KICBncm91cF9ieShjb2RlX3N0YXRpb25faHlkcm9iaW8sIGxvbmdpdHVkZV93Z3M4NCwgbGF0aXR1ZGVfd2dzODQsIEFubmVlKSU+JQ0KICBzdW1tYXJpc2UoUmVzdWx0YXQgPSBtYXgoY2xhc3NlKSklPiUNCiAgbXV0YXRlKFNlcmllID0gJ0NsYXNzZSAtIFF1YWxpdMOpIGJpb2xvZ2lxdWUgR2xvYmFsZScsIA0KICAgICAgICAgbGliZWxsZV9zdXBwb3J0ID0gJ1F1YWxpdMOpIGJpb2xvZ2lxdWUgR2xvYmFsZScsDQogICAgICAgICBzeW1ib2xlID0gJ1gnKQ0KDQp0YWJsZV9pbmRpY2VzX2NsYXNzZV9nbG9iYWxlX2FubmVlDQpgYGANCg0KIyBJbXBvcnQgZGVzIGRvbm7DqWVzIGVuIGJhc2UNCg0KIyMgRG9ubsOpZXMgYXUgZm9ybWF0IGRlIGxhIHRhYmxlIGVhdV9zdHJ1Y3R1cmUuYW5hbHlzZV9iaW9fZXN1IC0gc2VydmV1ciBQb3N0Z3JlU1FMIERFVg0KDQojIyMgTWlzZSBlbiBmb3JtZSBkZSBsYSB0YWJsZQ0KDQpgYGB7ciBhbmFseXNlX2Jpb19lc3UsIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9DQoNCmFuYWx5c2VfYmlvX2VzdSA8LSB0YWJsZV9pbmRpY2VzICU+JQ0KICBmaWx0ZXIoIWlzLm5hKHJlc3VsdGF0X2luZGljZSkpJT4lDQogIGxlZnRfam9pbihzZWxlY3QodGJsKGNvbl9wb3N0Z3Jlc3FsX2RldiwgZGJwbHlyOjppbl9zY2hlbWEoImVhdV9zdHJ1Y3R1cmUiLCJkYXRlIikpLCBkYXRlX2lkLCBkYXRlX2R1X2pvdXIpLCBieT1jKCJkYXRlX3ByZWxldmVtZW50IiA9ICJkYXRlX2R1X2pvdXIiKSwgY29weT1UUlVFKSU+JQ0KICBsZWZ0X2pvaW4oc2VsZWN0KHRibChjb25fcG9zdGdyZXNxbF9kZXYsIGRicGx5cjo6aW5fc2NoZW1hKCJlYXVfc3RydWN0dXJlIiwic3VwcG9ydCIpKSwgc3VwcG9ydF9pZCwgY29kZSksIGJ5PWMoImNvZGVfc3VwcG9ydCIgPSAiY29kZSIpLCBjb3B5PVRSVUUpJT4lDQogIGxlZnRfam9pbihzZWxlY3QodGJsKGNvbl9wb3N0Z3Jlc3FsX2RldiwgZGJwbHlyOjppbl9zY2hlbWEoImVhdV9zdHJ1Y3R1cmUiLCJyZW1hcnF1ZSIpKSwgcmVtYXJxdWVfaWQsIGNvZGUpLCBieT1jKCJjb2RlX3F1YWxpZmljYXRpb24iID0gImNvZGUiKSwgY29weT1UUlVFKSU+JQ0KICBtdXRhdGUocHJlbGV2ZW1lbnRfY29kZSA9IHBhc3RlMChjb2RlX3N0YXRpb25faHlkcm9iaW8sZGF0ZV9pZCksDQogICAgICAgICByZGRfaWQgPSAwLA0KICAgICAgICAgbWlsaWV1X2lkID0gMywNCiAgICAgICAgIGZyYWN0aW9uX2lkID0gMjIsDQogICAgICAgICBsaW1pdGVfcXVhbnRpZmljYXRpb24gPSAwLA0KICAgICAgICAgc291cmNlID0gJ09GQi9OQUlBREVTJywNCiAgICAgICAgIG1haiA9IGZvcm1hdChTeXMuRGF0ZSgpLCIlWS0lbS0lZCIpKSU+JQ0KICBzZWxlY3Qoc2l0ZV9pZCwNCiAgICAgICAgIGRhdGVfaWQsDQogICAgICAgICByZGRfaWQsDQogICAgICAgICBwcmVsZXZlbWVudF9jb2RlLA0KICAgICAgICAgbWlsaWV1X2lkLA0KICAgICAgICAgc3VwcG9ydF9pZCwNCiAgICAgICAgIGZyYWN0aW9uX2lkLA0KICAgICAgICAgcGFyYW1ldHJlX2lkLA0KICAgICAgICAgcmVzdWx0YXQgPSByZXN1bHRhdF9pbmRpY2UsDQogICAgICAgICByZW1hcnF1ZV9pZCwNCiAgICAgICAgIGxpbWl0ZV9xdWFudGlmaWNhdGlvbiwNCiAgICAgICAgIHNvdXJjZSwNCiAgICAgICAgIG1haikNCg0KYW5hbHlzZV9iaW9fZXN1DQpgYGANCg0KIyMjIFPDqWxlY3Rpb24gZGVzIG5vdXZlbGxlcyBsaWduZXMgw6AgaW5zw6lyZXINCg0KYGBge3IgaW5zZXJ0X2FuYWx5c2VfYmlvX2VzdX0NCg0KaW5zZXJ0X2FuYWx5c2VfYmlvX2VzdSA8LSBhbmFseXNlX2Jpb19lc3UNCg0KYGBgDQoNCiMjIyBJbnNlcnRpb24gZGVzIG5vdXZlbGxlcyBsaWduZXMNCg0KYGBge3IgaW5zZXJ0IHJlc3VsdGF0c19pbmNvbm51cywgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCmRiRXhlY3V0ZShjb25fcG9zdGdyZXNxbF9kZXYsICJUUlVOQ0FURSBUQUJMRSBlYXVfc3RydWN0dXJlLmFuYWx5c2VfYmlvX2VzdSIpDQoNCmRiQXBwZW5kVGFibGUoY29ubiA9IGNvbl9wb3N0Z3Jlc3FsX2RldiwgbmFtZSA9IElkKHNjaGVtYSA9ICJlYXVfc3RydWN0dXJlIix0YWJsZSA9ICJhbmFseXNlX2Jpb19lc3UiKSwgdmFsdWUgPSBpbnNlcnRfYW5hbHlzZV9iaW9fZXN1LCBmaWxlRW5jb2Rpbmc9ImxhdGluMSIpDQpgYGANCg0KIyMgRG9ubsOpZXMgYXUgZm9ybWF0IGRlIGxhIHRhYmxlIGVhdV90Ymkub2ViX2VhdV9xdWFsaXRlX2Jpb2xvZ2lxdWVfY2UgLSBzZXJ2ZXVyIE1hcmlhREIgT0VCDQoNCmBgYHtyIHNlcmllc19pbmRpY2VzX2FubnVlbHN9DQoNCnRhYmxlX3Nlcmllc19pbmRpY2VzIDwtIHRhYmxlX2luZGljZXNfY2xhc3Nlc19hbm5lZSAlPiUgDQogIG11dGF0ZShpbmRpY2UgPSBwYXN0ZShsaWJlbGxlLCBjb2RlX2luZGljZSwgc2VwPScgLSAnKSklPiUNCiAgcGl2b3RfbG9uZ2VyKGNvbHM9IGMoImNsYXNzZSIsInJlc3VsdGF0X2luZGljZSIsInJlc3VsdGF0X3F1YWxpZmljYXRpb24iKSklPiUNCiAgbXV0YXRlKFNlcmllID0gY2FzZV93aGVuKG5hbWUgPT0gJ2NsYXNzZScgfiBwYXN0ZSgnQ2xhc3NlJyxsaWJlbGxlX3N1cHBvcnQsc2VwID0gJyAtICcpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgbmFtZSA9PSAncmVzdWx0YXRfaW5kaWNlJyB+IHBhc3RlKCdJbmRpY2UnLGluZGljZSxzZXAgPSAnIC0gJyksDQogICAgICAgICAgICAgICAgICAgICAgICAgICBuYW1lID09ICdyZXN1bHRhdF9xdWFsaWZpY2F0aW9uJyB+IHBhc3RlKCdRdWFsaWZpY2F0aW9uJyxpbmRpY2Usc2VwID0gJyAtICcpDQogICAgICAgICAgICAgICAgICAgICAgICAgICApDQogICAgICAgICApJT4lDQogIGdyb3VwX2J5KGNvZGVfc3RhdGlvbl9oeWRyb2JpbywNCiAgICAgICAgICAgbG9uZ2l0dWRlX3dnczg0LA0KICAgICAgICAgICBsYXRpdHVkZV93Z3M4NCwNCiAgICAgICAgICAgbGliZWxsZV9zdXBwb3J0LA0KICAgICAgICAgICBTZXJpZSwNCiAgICAgICAgICAgc3ltYm9sZSwNCiAgICAgICAgICAgQW5uZWUpJT4lDQogIHN1bW1hcmlzZShSZXN1bHRhdCA9IG1heCh2YWx1ZSkpJT4lDQogIHVuZ3JvdXAoKSU+JQ0KICB1bmlvbih0YWJsZV9pbmRpY2VzX2NsYXNzZV9nbG9iYWxlX2FubmVlKQ0KDQp0YWJsZV9zZXJpZXNfaW5kaWNlcw0KYGBgDQojIyMgRMOpY2xpbmFpc29uIHBhciBjb21iaW5haXNvbiBTSVRFIC8gRUdBDQoNCmBgYHtyIHRhYmxlX3Nlcmllc19pbmRpY2VzX2VnYX0NCnRhYmxlX3Nlcmllc19pbmRpY2VzX2VnYSA8LSB0YWJsZV9zZXJpZXNfaW5kaWNlcyU+JQ0KICAjIHRhYmxlIGRlcyBjb3JyZXNwb25kYW5jZXMgc2l0ZXMgLyBVR0ENCiAgbGVmdF9qb2luKGNvcnJlc3BvbmRhbmNlX3NpdGVfZWdhLCBieT1jKCJjb2RlX3N0YXRpb25faHlkcm9iaW8iID0gImNkc2l0ZSIpLCBjb3B5ID0gVFJVRSklPiUNCiAgbGVmdF9qb2luKHNpdGVzX3JjcywgYnk9ImNvZGVfc3RhdGlvbl9oeWRyb2JpbyIpDQoNCnRhYmxlX3Nlcmllc19pbmRpY2VzX2VnYQ0KYGBgDQojIyMgTWlzZSBlbiBmb3JtZSBkZSBsYSB0YWJsZQ0KDQpgYGB7ciB0YWJsZSBvZWJfZWF1X3F1YWxpdGVfYmlvbG9naXF1ZV9jZX0NCg0Kb2ViX2VhdV9xdWFsaXRlX2Jpb2xvZ2lxdWVfY2UgPC0gdGFibGVfc2VyaWVzX2luZGljZXNfZWdhICU+JQ0KICBtdXRhdGUoUGVyaW9kZSA9IGFzLmNoYXJhY3RlcihBbm5lZSksDQogICAgICAgICBTb3VyY2UgPSAnT0ZCL05BSUFERVMnLA0KICAgICAgICAgTWlzZV9hX2pvdXIgPSBmb3JtYXQoU3lzLkRhdGUoKSwiJVktJW0tJWQiKQ0KICAgICAgICAgKSU+JSANCiAgc2VsZWN0KFR5cGVfZW50aXRpdGVfZ2VvZ3JhcGhpcXVlID0gdHlwZXNpdGUsDQogICAgICAgICBDb2RlX2VudGl0aXRlX2dlb2dyYXBoaXF1ZSA9IGNvZGVfc3RhdGlvbl9oeWRyb2JpbywNCiAgICAgICAgIExpYmVsbGVfZW50aXRpdGVfZ2VvZ3JhcGhpcXVlID0gbGJzaXRlLA0KICAgICAgICAgQ29vcmRYX1dHUzg0ID0gbG9uZ2l0dWRlX3dnczg0LA0KICAgICAgICAgQ29vcmRZX1dHUzg0ID0gbGF0aXR1ZGVfd2dzODQsDQogICAgICAgICBSZXNlYXVfUkNTID0gaW5jbHVzX3JjcywNCiAgICAgICAgIFR5cGVfZW50aXRpdGVfZ2VvZ3JhcGhpcXVlX2Fzc29jaWVlID0gdHlwZWVnYSwNCiAgICAgICAgIENvZGVfZW50aXRpdGVfZ2VvZ3JhcGhpcXVlX2Fzc29jaWVlID0gY2RlZ2EsDQogICAgICAgICBMaWJlbGxlX2VudGl0aXRlX2dlb2dyYXBoaXF1ZV9hc3NvY2llZSA9IGxiZWdhLA0KICAgICAgICAgUGVyaW9kZSwNCiAgICAgICAgIFNlcmllLA0KICAgICAgICAgdW5pdGUgPSBzeW1ib2xlLA0KICAgICAgICAgUmVzdWx0YXQsDQogICAgICAgICBTb3VyY2UsDQogICAgICAgICBNaXNlX2Ffam91cg0KICApDQoNCm9lYl9lYXVfcXVhbGl0ZV9iaW9sb2dpcXVlX2NlDQoNCmBgYA0KIyMjIFPDqWxlY3Rpb24gZGVzIGxpZ25lcyBub3V2ZWxsZXMgw6AgaW5zw6lyZXINCg0KDQpgYGB7ciB0YWJsZSBpbnNlcnRfb2ViX2VhdV9xdWFsaXRlX2Jpb2xvZ2lxdWVfY2V9DQoNCmluc2VydF9vZWJfZWF1X3F1YWxpdGVfYmlvbG9naXF1ZV9jZSA8LSBvZWJfZWF1X3F1YWxpdGVfYmlvbG9naXF1ZV9jZSAlPiUgDQogIHVuZ3JvdXAoKSAlPiUNCiAgIyBSZXRpcmVyIGxlcyBsaWduZXMgZGVzIHLDqXN1bHRhdHMgZMOpasOgIGV4aXN0YW50cyBkYW5zIGxhIHRhYmxlDQogIGFudGlfam9pbih0YmwoY29uX2VhdV90YmksIm9lYl9lYXVfcXVhbGl0ZV9iaW9sb2dpcXVlX2NlIiwgYnk9YygiVHlwZV9lbnRpdGl0ZV9nZW9ncmFwaGlxdWUiLCAiQ29kZV9lbnRpdGl0ZV9nZW9ncmFwaGlxdWUiLCAiVHlwZV9lbnRpdGl0ZV9nZW9ncmFwaGlxdWVfYXNzb2NpZWUiLCAiQ29kZV9lbnRpdGl0ZV9nZW9ncmFwaGlxdWVfYXNzb2NpZWUiLCAiUGVyaW9kZSIsICJTZXJpZSIpKSwgY29weSA9IFRSVUUpJT4lDQogICMgUmV0aXJlciBsZXMgc3RhdGlvbnMgaHlkcm8gc2FucyBFR0EgaWRlbnRpZmnDqWUNCiAgZmlsdGVyKCFpcy5uYShUeXBlX2VudGl0aXRlX2dlb2dyYXBoaXF1ZSkpJT4lDQogICMgUmV0aXJlciBsZXMgbGlnbmVzIGF2ZWMgdW5lIHZhbGV1ciBOQQ0KICBkcm9wX25hKCkNCg0KaW5zZXJ0X29lYl9lYXVfcXVhbGl0ZV9iaW9sb2dpcXVlX2NlDQpgYGANCg0KIyMjIEluc2VydGlvbiBkZXMgbm91dmVsbGVzIGxpZ25lcw0KDQpgYGB7ciBpbnNlcnQgb2ViX2VhdV9xdWFsaXRlX2Jpb2xvZ2lxdWVfY2UsIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9DQpkYkV4ZWN1dGUoY29uX2VhdV90YmksICJUUlVOQ0FURSBUQUJMRSBvZWJfZWF1X3F1YWxpdGVfYmlvbG9naXF1ZV9jZSIpDQoNCmRiQXBwZW5kVGFibGUoY29ubiA9IGNvbl9lYXVfdGJpLCBuYW1lID0gIm9lYl9lYXVfcXVhbGl0ZV9iaW9sb2dpcXVlX2NlIiwgdmFsdWUgPSBpbnNlcnRfb2ViX2VhdV9xdWFsaXRlX2Jpb2xvZ2lxdWVfY2UsIGZpbGVFbmNvZGluZz0ibGF0aW4xIikNCmBgYA0KDQojIyMgRXhwb3J0IHBvdXIgbGUgR0lERQ0KDQpgYGB7ciBleHBvcnQgb2ViX2VhdV9xdWFsaXRlX2Jpb2xvZ2lxdWVfY2V9DQojIERlcHVpcyBsYSBiYXNlIGRlIGRvbm7DqWVzDQojdGJsKGNvbl9lYXVfdGJpLCAib2ViX2VhdV9xdWFsaXRlX2Jpb2xvZ2lxdWVfY2VfbmV3IiklPiUNCiMgb3UgZGVwdWlzIGxhIHRhYmxlIGVuIG3DqW1vaXJlDQpvZWJfZWF1X3F1YWxpdGVfYmlvbG9naXF1ZV9jZSAlPiUgIA0Kd3JpdGUudGFibGUoZmlsZSA9IHBhc3RlMChwYXJhbXMkcGF0aF9kYXRhdml6LCJcXEdJREVcXCIsIm9lYl9lYXVfcXVhbGl0ZV9iaW9sb2dpcXVlX2NlLmNzdiIpLCBxdW90ZSA9IFRSVUUsIHNlcCA9ICI7IiwNCiAgICAgICAgICAgIGVvbCA9ICJcbiIsIG5hID0gIiIsIGRlYyA9ICIsIiwNCiAgICAgICAgICAgIGZpbGVFbmNvZGluZyA9ICJVVEYtOCIpDQpgYGANCg0KIyMjIEV4cG9ydCBwb3VyIEdFT0INCg0KYGBge3Igb2ViX2VhdV9xdWFsaXRlX2dlb2J9DQpvZWJfZWF1X3F1YWxpdGVfZ2VvYiA8LSB0YWJsZV9zZXJpZXNfaW5kaWNlcyAgJT4lDQogIHVuZ3JvdXAoKSU+JQ0KICBsZWZ0X2pvaW4oc2VsZWN0KHNpdGVzX3NhZ2VzLGNvZGVfc3RhdGlvbl9oeWRyb2JpbyxsaWJlbGxlX3N0YXRpb25faHlkcm9iaW8pLCBieT0iY29kZV9zdGF0aW9uX2h5ZHJvYmlvIikgICU+JQ0KICBtdXRhdGUoQ29kZV9lbnRpdGl0ZV9nZW9ncmFwaGlxdWUgPSBjb2RlX3N0YXRpb25faHlkcm9iaW8sDQogICAgICAgICBMaWJlbGxlX2VudGl0aXRlX2dlb2dyYXBoaXF1ZSA9IGxpYmVsbGVfc3RhdGlvbl9oeWRyb2JpbywNCiAgICAgICAgIENvb3JkWF9XR1M4NCA9IGxvbmdpdHVkZV93Z3M4NCwNCiAgICAgICAgIENvb3JkWV9XR1M4NCA9IGxhdGl0dWRlX3dnczg0LA0KICAgICAgICAgU2VyaWUsDQogICAgICAgICB1bml0ZSA9IHN5bWJvbGUsDQogICAgICAgICBUeXBlX2VudGl0aXRlX2dlb2dyYXBoaXF1ZSA9ICdTSVRFJywNCiAgICAgICAgIFNvdXJjZSA9ICdPRkIvTkFJQURFUycsDQogICAgICAgICBNaXNlX2Ffam91ciA9IGZvcm1hdChTeXMuRGF0ZSgpLCIlWS0lbS0lZCIpKSAlPiUNCiAgc2VsZWN0KFR5cGVfZW50aXRpdGVfZ2VvZ3JhcGhpcXVlLA0KICAgICAgICAgQ29kZV9lbnRpdGl0ZV9nZW9ncmFwaGlxdWUsDQogICAgICAgICBMaWJlbGxlX2VudGl0aXRlX2dlb2dyYXBoaXF1ZSwNCiAgICAgICAgIENvb3JkWF9XR1M4NCwNCiAgICAgICAgIENvb3JkWV9XR1M4NCwNCiAgICAgICAgIFNlcmllLA0KICAgICAgICAgdW5pdGUsDQogICAgICAgICBTb3VyY2UsDQogICAgICAgICBNaXNlX2Ffam91ciwNCiAgICAgICAgIEFubmVlLA0KICAgICAgICAgUmVzdWx0YXQpICU+JQ0KICBwaXZvdF93aWRlcih2YWx1ZXNfZnJvbSA9IFJlc3VsdGF0LCBuYW1lc19mcm9tID0gQW5uZWUsIG5hbWVzX3NvcnQ9VFJVRSkNCmBgYA0KDQoNCmBgYHtyIGV4cG9ydCBvZWJfZWF1X3F1YWxpdGVfYmlvbG9naXF1ZV9nbG9iYWxlfQ0KDQpvZWJfZWF1X3F1YWxpdGVfZ2VvYiAgJT4lDQogIGZpbHRlcihTZXJpZSA9PSAnQ2xhc3NlIC0gUXVhbGl0w6kgYmlvbG9naXF1ZSBHbG9iYWxlJykgICU+JQ0Kd3JpdGUudGFibGUoZmlsZSA9IHBhc3RlMChwYXJhbXMkcGF0aF9kYXRhdml6LCJcXEdFT0JcXCIsIm9lYl9lYXVfcXVhbGl0ZV9iaW9sb2dpcXVlX2dsb2JhbGUuY3N2IiksIHF1b3RlID0gVFJVRSwgc2VwID0gIjsiLA0KICAgICAgICAgICAgZW9sID0gIlxuIiwgbmEgPSAiIiwgZGVjID0gIiwiLA0KICAgICAgICAgICAgZmlsZUVuY29kaW5nID0gIlVURi04IikNCmBgYA0KDQpgYGB7ciBleHBvcnQgb2ViX2VhdV9xdWFsaXRlX2RpYXRvbWVlc30NCg0Kb2ViX2VhdV9xdWFsaXRlX2dlb2IgJT4lDQogIGZpbHRlcihTZXJpZSA9PSAnQ2xhc3NlIC0gRGlhdG9tw6llcyBiZW50aGlxdWVzJykgJT4lDQp3cml0ZS50YWJsZShmaWxlID0gcGFzdGUwKHBhcmFtcyRwYXRoX2RhdGF2aXosIlxcR0VPQlxcIiwib2ViX2VhdV9xdWFsaXRlX2RpYXRvbWVlcy5jc3YiKSwgcXVvdGUgPSBUUlVFLCBzZXAgPSAiOyIsDQogICAgICAgICAgICBlb2wgPSAiXG4iLCBuYSA9ICIiLCBkZWMgPSAiLCIsDQogICAgICAgICAgICBmaWxlRW5jb2RpbmcgPSAiVVRGLTgiKQ0KYGBgDQoNCmBgYHtyIGV4cG9ydCBvZWJfZWF1X3F1YWxpdGVfbWFjcm9pbnZlcnRlYnJlc30NCg0Kb2ViX2VhdV9xdWFsaXRlX2dlb2IgJT4lDQogIGZpbHRlcihTZXJpZSA9PSAnQ2xhc3NlIC0gTWFjcm9pbnZlcnTDqWJyw6lzIGFxdWF0aXF1ZXMnKSAlPiUNCndyaXRlLnRhYmxlKGZpbGUgPSBwYXN0ZTAocGFyYW1zJHBhdGhfZGF0YXZpeiwiXFxHRU9CXFwiLCJvZWJfZWF1X3F1YWxpdGVfbWFjcm9pbnZlcnRlYnJlcy5jc3YiKSwgcXVvdGUgPSBUUlVFLCBzZXAgPSAiOyIsDQogICAgICAgICAgICBlb2wgPSAiXG4iLCBuYSA9ICIiLCBkZWMgPSAiLCIsDQogICAgICAgICAgICBmaWxlRW5jb2RpbmcgPSAiVVRGLTgiKQ0KYGBgDQoNCmBgYHtyIGV4cG9ydCBvZWJfZWF1X3F1YWxpdGVfbWFjcm9waHl0ZXN9DQoNCm9lYl9lYXVfcXVhbGl0ZV9nZW9iICU+JQ0KICBmaWx0ZXIoU2VyaWUgPT0gJ0NsYXNzZSAtIE1hY3JvcGh5dGVzJykgJT4lDQp3cml0ZS50YWJsZShmaWxlID0gcGFzdGUwKHBhcmFtcyRwYXRoX2RhdGF2aXosIlxcR0VPQlxcIiwib2ViX2VhdV9xdWFsaXRlX21hY3JvcGh5dGVzLmNzdiIpLCBxdW90ZSA9IFRSVUUsIHNlcCA9ICI7IiwNCiAgICAgICAgICAgIGVvbCA9ICJcbiIsIG5hID0gIiIsIGRlYyA9ICIsIiwNCiAgICAgICAgICAgIGZpbGVFbmNvZGluZyA9ICJVVEYtOCIpDQpgYGANCg==